<template>
    <div class="order-list-time" ref="rootEl">
        <!-- 表头 -->
        <div class="table-header flex flex-btw">
            <span v-for="column in props.columns" :key="column.key">{{ column.title }}</span>
        </div>
        <!-- 列表主体：只渲染可视区域 -->
        <div class="table-body" ref="bodyEl" @scroll="onBodyScroll">
            <!-- 占位元素，撑出真实高度 -->
            <div class="virtual-spacer" :style="{ height: totalHeight + 'px' }"></div>
            <!-- 可视行：绝对定位 -->
            <TransitionGroup name="fade-up" tag="div" @before-enter="beforeEnter" @enter="enter" @leave="leave">
                <template v-for="item in visibleList" :key="item.id">
                    <div
                        class="order-item flex flex-align-center flex-btw"
                        :class="{ 'is-new': item.isNew }"
                        :style="{ transform: `translateY(${item.offsetY}px)` }"
                        v-scroll-animate="{ onDone: item.onDone /* 可选 */ }"
                    >
                        <span class="item-name">{{ item[columns[0].key] }}</span>
                        <p class="item-product">
                            {{ props.actionName ? props.actionName : "" }}
                            <span>{{ item[columns[1].key] }}</span>
                        </p>
                        <span class="item-time">{{ item[columns[2].key] }}</span>
                    </div>
                </template>
            </TransitionGroup>
            <!-- 底部锚点：进入视口即加载下一页 -->
            <div ref="anchorEl" class="anchor"></div>
        </div>
    </div>
</template>

<script setup>
import { ref, computed, nextTick, onMounted, onUnmounted } from "vue";
import $http from "@/api/apiMap";
import { differenceByKey } from "@/utils/base";
const props = defineProps({
    columns: {
        type: Array,
        default: () => {
            return [];
        },
    },
    actionName: String,
    onDone: Function,
    config: Object,
});

/* --------------- 分页状态 --------------- */
const pageSize = 10; // 每页条数
let pageNo = 0; // 当前页码（后端用）
const isLastPage = ref(false); // 是否到底
const loading = ref(false); // 加载锁
const list = ref([]); // 全量数据

/* --------------- DOM 引用 --------------- */
const rootEl = ref(null); // 整个组件最外层容器（.order-list-time）
const bodyEl = ref(null); // 实际发生滚动的列表容器（.table-body）
const anchorEl = ref(null); // 用于“无限加载”的底部锚点元素（.anchor）
const itemHeight = 36; // 单行数据的高度（单位 px，必须与 CSS 保持一致）
const buffer = 5; // 虚拟滚动“上下缓冲区”行数，可减少白屏闪烁

/* --------------- 虚拟滚动 --------------- */
const scrollTop = ref(0); // 容器 scrollTop
const clientHeight = ref(0); // 容器可视高度
const visibleList = computed(() => {
    if (endIndex.value < startIndex.value) {
        return [];
    }
    const tempArray = list.value.filter((item, i) => i >= startIndex.value);
    return tempArray.map((raw, i) => {
        return {
            ...raw,
            id: startIndex.value + i,
            offsetY: (startIndex.value + 0.8 + i) * itemHeight,
            onDone: raw.onDone,
        };
    });
});
const totalHeight = computed(() => list.value.length * itemHeight);
function onBodyScroll(e) {
    scrollTop.value = e.target.scrollTop;
}

/* --------------- 无限加载 --------------- */
async function loadNextPage() {
    if (loading.value || isLastPage.value) return;
    loading.value = true;
    const newList = await fetchOrderPage(pageNo + 1, pageSize);
    if (!newList.length) {
        isLastPage.value = true;
    } else {
        list.value.push(...newList);
        pageNo++;
    }
    loading.value = false;
}
/* --------------- 单条向上滚动 --------------- */

function scrollOneItemSmooth() {
    const target = bodyEl.value.scrollTop + itemHeight;
    const start = bodyEl.value.scrollTop;
    const distance = target - start;
    const duration = 400; // ms
    let startTime = null;

    function step(ts) {
        if (!startTime) startTime = ts;
        const progress = Math.min((ts - startTime) / duration, 1);
        bodyEl.value.scrollTop = start + distance * progress;
        if (progress < 1) requestAnimationFrame(step);
    }
    requestAnimationFrame(step);
}

/* --------------- 工具 --------------- */
/**
 * 格式化时间 → yy-mm-dd hh:mm:ss
 * @param {number|string} input  时间戳（长度不足11位自动补0）或时间字符串
 * @returns {string}            形如 25-06-02 14:07:08
 */
function formatTime(input) {
    let date;

    // 1. 如果是数字（或能转成数字），则视为时间戳
    if (!isNaN(input)) {
        let ts = String(input).padEnd(13, "0"); // 不足13位尾部补0
        date = new Date(Number(ts));
    }
    // 2. 否则视为 yyyy-mm-dd hh:mm:ss 或 yyyy/mm/dd hh:mm:ss
    else {
        // 把 / 换成 -，Safari 才能正确解析
        date = new Date(String(input).replace(/\//g, "-"));
    }

    if (isNaN(date)) throw new Error("Invalid time input");

    const pad = n => n.toString().padStart(2, "0");

    const y = date.getFullYear() % 100; // 取后两位
    const M = date.getMonth() + 1;
    const d = date.getDate();
    const h = date.getHours();
    const m = date.getMinutes();
    const s = date.getSeconds();

    return `${pad(y)}-${pad(M)}-${pad(d)} ${pad(h)}:${pad(m)}:${pad(s)}`;
}

/* --------------- 模拟接口 --------------- */
async function fetchOrderPage(page, size) {
    return new Promise(resolve => {
        const requestMethod = props.config.requestMethod;
        if (!requestMethod || !$http[requestMethod]) {
            resolve([]);
        }
        const cols = props.columns;
        const timeKey = cols[cols.length - 1].key;
        $http[requestMethod]().then(res => {
            let newList = res.data.map(item => {
                const newTime = formatTime(item[timeKey]);
                item[timeKey] = newTime;
                return item;
            });
            resolve(newList);
        });
    });
}

/* --------------- transition JS 钩子 --------------- */
function beforeEnter(el) {
    el.style.opacity = 0;
    el.style.transform += ` translateY(${itemHeight}px)`;
}
function enter(el, done) {
    const dur = 400;
    el.offsetHeight; // force reflow
    el.style.transition = `opacity ${dur}ms, transform ${dur}ms`;
    el.style.opacity = 1;
    el.style.transform = el.style.transform.replace(`translateY(${itemHeight}px)`, "translateY(0px)");
    setTimeout(done, dur);
}
function leave() {}

/* 新增：可视行数（整数） */
const visibleRowCount = ref(0);

/* 根据父容器高度重新计算可视行数 */
function calcVisibleRowCount() {
    if (!bodyEl.value) return;
    // 保底至少 1 行
    const h = bodyEl.value.clientHeight || itemHeight;
    // 用 Math.floor 保证整数行
    visibleRowCount.value = Math.max(1, Math.floor(h / itemHeight));
}

/* 重新计算起止索引 */
const startIndex = computed(() => Math.max(0, Math.floor(scrollTop.value / itemHeight) - buffer));
const endIndex = computed(() => Math.min(list.value.length - 1, startIndex.value + visibleRowCount.value + buffer));

/* ResizeObserver：父容器高度变化时实时修正 */
let ro = null;
/* --------------- 生命周期 --------------- */
onMounted(async () => {
    /* 无限加载 */
    const io = new IntersectionObserver(
        entries => {
            if (entries[0].isIntersecting) {
                loadNextPage();
            }
        },
        { root: bodyEl.value }
    );
    io.observe(anchorEl.value);
    /* 1. 先拉首屏数据 */
    await loadNextPage();
    /* 2. 等 DOM 真正渲染出来再算高度 */
    await nextTick();
    bodyEl.value.scrollTop = bodyEl.value.scrollHeight;
    clientHeight.value = bodyEl.value.clientHeight;
    calcVisibleRowCount(); // 首屏
    /* 3. 如果父容器之后还会变化（弹窗、伸缩等），继续监听 */
    ro = new ResizeObserver(() => nextTick(calcVisibleRowCount));
    ro.observe(bodyEl.value);
});
onUnmounted(() => ro?.disconnect());
/**
 * 供父组件主动调用：向列表追加新数据并触发动画
 *   newList  : 新增的数据数组
 *   onDone   : 动画结束后执行的回调
 */
function appendData(newList = []) {
    if (!Arrar.isArray(newList) || newList.length == 0) {
        return false;
    }
    let diffList = differenceByKey(newList, list.value, props.idKey);
    if (!Arrar.isArray(diffList) || diffList.length == 0) {
        return false;
    }
    const { onDone, columns } = props;
    const timeKey = columns[columns.length - 1].key;
    diffList = diffList.map(raw => {
        raw.isNew = true;
        raw.onDone = onDone;
        raw[timeKey] = formatTime(raw[timeKey]);
        return raw;
    });
    // 2. 先把新数据 push 进去
    list.value.push(...diffList);
    // 3. 等 DOM 渲染完再向上滚一行
    nextTick(() => scrollOneItemSmooth());
    // 4. 监听最后一条动画结束，触发回调
    //    由指令内部完成，这里什么都不用做
}

/* 暴露给父组件 */
defineExpose({ appendData });
</script>

<style scoped lang="scss">
/* 原有样式保持不变，仅补充动画 */
.fade-up-move {
    transition:
        transform,
        background-image 0.4s;
}
/* 金色 -> 蓝色渐变动画，只播放一次 */
.is-new.order-item {
    animation: isNewAnim 1.5s ease-out;
}
@keyframes isNewAnim {
    0% {
        opacity: 0;
        background-image: linear-gradient(180deg, rgba(255, 216, 67, 0.3) 0%, rgb(255, 209, 69, 0.2) 100%);
    }
    25% {
        opacity: 0.25;
        background-image: linear-gradient(180deg, rgba(255, 216, 67, 0.3) 0%, rgb(255, 209, 69, 0.2) 100%);
    }
    50% {
        opacity: 0.5;
        background-image: linear-gradient(180deg, rgba(255, 216, 67, 0.3) 0%, rgb(255, 209, 69, 0.2) 100%);
    }
    75% {
        opacity: 0.75;
        background-image: linear-gradient(180deg, rgba(255, 216, 67, 0.3) 0%, rgb(255, 209, 69, 0.2) 100%);
    }
    90% {
        opacity: 0.9;
        background-image: linear-gradient(180deg, rgba(255, 216, 67, 0.3) 0%, rgb(255, 209, 69, 0.2) 100%);
    }
    100% {
        opacity: 1;
    }
}
.table-header {
    width: 100%;
    padding: 0 10px;
    margin-bottom: 5px;
    span {
        line-height: 1.5;
        flex-shrink: 1;
        font-size: 12px;
        color: #fff;
        opacity: 0.5;
        &:first-child {
            padding-left: 5px;
            width: 110px;
        }
        &:last-child {
            width: 130px;
        }
        &:nth-child(2) {
            width: calc(100% - 240px);
        }
    }
}
.order-list-time {
    height: 100%; /* 给外层一个固定高度，出现滚动条 */
    display: flex;
    flex-direction: column;
}
.table-body {
    flex: 1;
    position: relative;
    padding: 0 10px;
    overflow-y: scroll; /* 必须保留滚动能力 */
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* IE 10+ */
    will-change: scroll-position; /* 提前让浏览器把滚动层独立合成 */
    &::-webkit-scrollbar {
        display: none; /* Chrome / Edge / Safari */
    }
}
.virtual-spacer {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}
.order-item {
    position: absolute;
    left: 10px;
    width: calc(100% - 20px);
    height: 30px;
    /* 其余样式保持你原来的 */
    background-image: linear-gradient(180deg, rgba(0, 128, 254, 0.1) 2%, rgba(0, 128, 254, 0.2) 100%);
    margin: 6px 0;
    font-size: 12px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .item-name {
        color: #6ebfff;
        flex-shrink: 1;
        width: 110px;
        padding: 0 5px;
    }
    .item-time {
        flex-shrink: 1;
        width: 130px;
        padding: 0 5px;
    }
    .item-product {
        flex: 1;
        width: calc(100% - 240px);
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        span {
            color: #6ebfff;
        }
    }
}
.anchor {
    position: absolute;
    bottom: 0;
    height: 1px;
}
</style>
